/* 
Scenic Graphics Library
Copyright (C) 2007 Jouni Tulkki

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USApackage scenic;
*/

package scenic.awt;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.RenderableImage;
import java.text.AttributedCharacterIterator;

import scenic.FilledPath;
import scenic.ImageRenderer;
import scenic.SceneContainer;
import scenic.SceneNode;
import scenic.ScenicColor;
import scenic.ScenicGraphics;
import scenic.ScenicImage;
import scenic.StrokedPath;
import scenic.path.Path;

/**
 * Implementation of the Graphics2D class that uses the Scenic library
 * for rendering. This implementation does not use direct rendering,
 * instead it creates scene trees. After the scene tree is finished
 * it can be rendered on the screen or to an offscreen image.
 */
public class ScenicGraphics2D extends AbstractGraphics2D {
	private ScenicGraphics g;
	private SceneContainer root;
	private ScenicImage image;
	private double curveFlatness = 1.0 / 10.0;
	private AffineTransform transform = new AffineTransform();
			
	/**
	 * Constructs a new ScenicGraphics2D using the given scene as the root
	 * node.
	 * 
	 * @param scene the root node
	 */
	public ScenicGraphics2D(SceneContainer scene) {
		super(true);
		root = scene;
		g = new ScenicGraphics(root);
		g.setAntialias(true);
		g.push();
	}

	/**
	 * Constructs a new ScenicGraphics2D using the given image as the
	 * rendering target. The image is rendered when the dispose method
	 * is called.
	 * 
	 * @param image the render target
	 */
	public ScenicGraphics2D(ScenicImage image) {
		super(true);
		root = new SceneContainer();
		g = new ScenicGraphics(root);
		g.setAntialias(true);
		g.push();
		this.image = image;
	}
	
	/**
	 * Constructs a SceneGraphics2D that is a copy of the given
	 * SceneGraphics2D object. Note that the implementation of 
	 * ScenicGraphics2D is not totally compliant with the normal
	 * Graphics2D implementation. To avoid any problems, different
	 * SceneGraphics2D objects that share the same parent object should
	 * not be in parallel.
	 * 
	 * @param parent the parent object
	 */
	public ScenicGraphics2D(ScenicGraphics2D parent) {
		super(true);
		g = parent.g.create();
		transform = (AffineTransform)parent.transform.clone();
	}
	
	@Override
	public AffineTransform getTransform() {
		return (AffineTransform)g.getTransform().clone();
	}


	@Override
	public void rotate(double theta, double x, double y) {
		transform.rotate(theta, x, y);
	}


	@Override
	public void rotate(double theta) {
		transform.rotate(theta);
	}


	@Override
	public void scale(double sx, double sy) {
		transform.scale(sx, sy);
	}


	@Override
	public void setTransform(AffineTransform Tx) {
		transform = (AffineTransform)Tx.clone();
	}


	@Override
	public void shear(double shx, double shy) {
		transform.shear(shx, shy);
	}


	@Override
	public void transform(AffineTransform Tx) {
		transform.concatenate(Tx);
	}


	@Override
	public void translate(double tx, double ty) {
		transform.translate(tx, ty);
	}

	@Override
	public void translate(int tx, int ty) {
		transform.translate(tx, ty);
	}

	private SceneNode paintShape(scenic.Shape shape) {
		Paint paint = gc.getPaint();
		
		if(paint instanceof Color) {
			shape.setColor(new ScenicColor((Color)paint));
			return shape;
		}
		return shape;
	}
	
	@Override
	public void draw(Shape s) {
		if(gc.getStroke() instanceof BasicStroke) {
			Path path = new Path(s, transform, curveFlatness);
			BasicStroke stroke = (BasicStroke)gc.getStroke();
			StrokedPath strokedPath = new StrokedPath(path);

			strokedPath.setLineWidth(stroke.getLineWidth());
			strokedPath.setEndCap(stroke.getEndCap());
			strokedPath.setLineJoin(stroke.getLineJoin());
			strokedPath.setMiterLimit(stroke.getMiterLimit());
			strokedPath.setDashArray(stroke.getDashArray());
			strokedPath.setDashPhase(stroke.getDashPhase());
			
			g.add(paintShape(strokedPath));
		} else {
			fill(gc.getStroke().createStrokedShape(s));
		}
	}

	@Override
	public void fill(Shape s) {
		Path path = new Path(s, transform, curveFlatness);
		FilledPath filledPath = new FilledPath(path);

		g.add(paintShape(filledPath));
	}

	@Override
	public void clip(Shape s) {
		Path path = new Path(s, transform, curveFlatness);
		FilledPath filledPath = new FilledPath(path);
		
		g.clipTo(filledPath);	
	}
	
	@Override
	public void setClip(Shape s) {
		g.pop();
		g.push();
		if(s != null)
			clip(s);
	}
	
	@Override
	public void drawRenderedImage(RenderedImage img, AffineTransform xform) {
	}

	@Override
	public void drawRenderableImage(RenderableImage img, AffineTransform xform) {
	}

	@Override
	public void drawString(String str, float x, float y) {
		g.push();
		g.transform(transform);
		g.setFillColor(new ScenicColor(gc.getColor()));
		g.setFont(gc.getFont());
		g.setTextPosition(x, y);
		g.drawText(str);
		g.pop();
	}

	@Override
	public void drawString(AttributedCharacterIterator iterator, float x,
			float y) {
		// TODO Auto-generated method stub

	}

	@Override
	public GraphicsConfiguration getDeviceConfiguration() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public java.awt.Graphics create() {
		return new ScenicGraphics2D(this);
	}

	@Override
	public void setXORMode(Color c1) {
		// TODO Auto-generated method stub

	}

	@Override
	public FontMetrics getFontMetrics(Font f) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void copyArea(int x, int y, int width, int height, int dx, int dy) {
		// TODO Auto-generated method stub

	}

	@Override
	public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
		ScenicImage simg = convertToScenicImage(img);
	   	
		if(simg == null)
			return false;
			
		g.push();
		g.transform(transform);
		g.translate(x, y);
		g.drawImage(simg);
		g.pop();

		return true;
	}

	@Override
	public boolean drawImage(Image img, int x, int y, int width, int height,
			ImageObserver observer) {
		ScenicImage simg = convertToScenicImage(img);
	   	
		if(simg == null)
			return false;

		g.push();
		g.transform(transform);
		g.translate(x, y);
		g.scale((double)width / simg.getWidth(), (double)height / simg.getHeight());
		g.drawImage(simg);
		g.pop();
		return true;
	}

    /**
     * Draws as much of the specified area of the specified image as is
     * currently available, scaling it on the fly to fit inside the
     * specified area of the destination drawable surface. Transparent pixels
     * do not affect whatever pixels are already there.
     * <p>
     * This method returns immediately in all cases, even if the
     * image area to be drawn has not yet been scaled, dithered, and converted
     * for the current output device.
     * If the current output representation is not yet complete then
     * <code>drawImage</code> returns <code>false</code>. As more of
     * the image becomes available, the process that draws the image notifies
     * the specified image observer.
     * <p>
     * This method always uses the unscaled version of the image
     * to render the scaled rectangle and performs the required
     * scaling on the fly. It does not use a cached, scaled version
     * of the image for this operation. Scaling of the image from source
     * to destination is performed such that the first coordinate
     * of the source rectangle is mapped to the first coordinate of
     * the destination rectangle, and the second source coordinate is
     * mapped to the second destination coordinate. The subimage is
     * scaled and flipped as needed to preserve those mappings.
     * @param       img the specified image to be drawn
     * @param       dx1 the <i>x</i> coordinate of the first corner of the
     *                    destination rectangle.
     * @param       dy1 the <i>y</i> coordinate of the first corner of the
     *                    destination rectangle.
     * @param       dx2 the <i>x</i> coordinate of the second corner of the
     *                    destination rectangle.
     * @param       dy2 the <i>y</i> coordinate of the second corner of the
     *                    destination rectangle.
     * @param       sx1 the <i>x</i> coordinate of the first corner of the
     *                    source rectangle.
     * @param       sy1 the <i>y</i> coordinate of the first corner of the
     *                    source rectangle.
     * @param       sx2 the <i>x</i> coordinate of the second corner of the
     *                    source rectangle.
     * @param       sy2 the <i>y</i> coordinate of the second corner of the
     *                    source rectangle.
     * @param       observer object to be notified as more of the image is
     *                    scaled and converted.
     * @see         java.awt.Image
     * @see         java.awt.image.ImageObserver
     * @see         java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int, int, int, int)
     * @since       JDK1.1
     */
    public boolean drawImage(Image img,
                             int dx1, int dy1, int dx2, int dy2,
                             int sx1, int sy1, int sx2, int sy2,
                             ImageObserver observer){
     	int width = dx2 - dx1;
    	int height = dy2 - dy1;
		ScenicImage simg = convertToScenicImage(img);
   	
		if(simg == null)
			return false;
			
		g.push();
		g.transform(transform);
		g.translate(dx1, dy1);
		g.scale((double)width / (sx2 - sx1), (double)height / (sy2 - sy1));
		g.drawImage(simg, new Rectangle2D.Double(sx1, sy1, sx2 - sx1, sy2 - sy1));
		g.pop();
		return true;
    }
    
    private ScenicImage convertToScenicImage(Image img) {
		if(img instanceof ScenicImage) {
			return (ScenicImage)img;
		}
		if(img instanceof BufferedImage) {
			return ScenicImage.getImageFromCache((BufferedImage)img);
		}
		return null;
    }
    
	@Override
	public void dispose() {
		if(image != null) {
			ImageRenderer renderer = new ImageRenderer(image, root);
			
			renderer.setBgColor(null);
			renderer.render();
		}
	}
}
